package cz.data.domain.market.mapping.handler.interceptor;

import cz.data.domain.market.mapping.handler.DataApiHandlerMapping;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.nacos.shaded.com.google.common.util.concurrent.RateLimiter;
import cz.data.common.core.DataConstant;
import cz.data.common.support.db.core.PageResult;
import cz.data.common.exception.DataException;
import cz.data.common.utils.IPUtil;
import cz.data.common.utils.JsonCommon;
import cz.data.common.utils.SecurityUtil;
import cz.data.common.utils.SpringContextHolder;
import cz.data.common.utils.ThrowableUtil;
import cz.data.domain.market.mapping.factory.AbstractFactory;
import cz.data.domain.market.mapping.factory.FactoryProducer;
import cz.data.domain.market.mapping.factory.crypto.Crypto;
import cz.data.domain.market.mapping.listener.event.ApiAccessLogEvent;
import cz.data.domain.market.model.dto.ApiLogDto;
import cz.data.domain.market.model.dto.FieldRule;
import cz.data.domain.market.model.dto.RateLimit;
import cz.data.domain.market.model.dto.ReqParam;
import cz.data.domain.market.model.entity.ApiMaskEntity;
import cz.data.domain.market.model.entity.DataApiEntity;
import cz.data.domain.market.model.enums.ParamType;
import cz.data.domain.market.rpc.ApiMaskServiceFeign;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Component
public class DataApiRequestInterceptor extends AbstractRequestInterceptor {

    @Resource
    private ApiMaskServiceFeign apiMaskServiceFeign;

    /**
     * 请求之前执行
     *
     * 当返回对象时，直接将此对象返回到页面，返回null时，继续执行后续操作
     * @throws Exception error
     */
    public void preHandle(HttpServletRequest request, HttpServletResponse response,
                          DataApiEntity api, Map<String, Object> params) throws Exception {
        String ipAddr = IPUtil.getIpAddr(request);
        String userId = SecurityUtil.getUserId();

        // 黑名单校验
        String deny = api.getDeny();
        if (StringUtils.isNotBlank(deny)) {
            List<String> denyList = Arrays.asList(deny.split(","));
            if (CollectionUtils.isNotEmpty(denyList)) {
                for (String ip : denyList) {
                    if (ip.equals(ipAddr)) {
                        throw new DataException(ip + "已被加入IP黑名单");
                    }
                }
            }
        }
        // 参数校验
        if (MapUtils.isNotEmpty(params)) {
            for (ReqParam param : api.getReqParams()) {
                if (params.containsKey(param.getParamName())) {
                    // 参数类型是否正确
                    ParamType.parse(ParamType.getParamType(param.getParamType()), params.get(param.getParamName()));
                }
            }
        }
        // 限流校验
        RateLimit rateLimit = api.getRateLimit();
        if (Objects.nonNull(rateLimit) && DataConstant.TrueOrFalse.TRUE.getKey().equals(rateLimit.getEnable())) {
            // todo 获取限流器,此处为内存存储限制单个服务实例限流
            RateLimiter rateLimiter = DataApiHandlerMapping.getRateLimiter(api, userId);
            if (Objects.nonNull(rateLimiter)) {
                // 获取通行令牌
                boolean acquire = rateLimiter.tryAcquire();
                if (!acquire) {
                    throw new DataException("API调用频繁!请稍后重试");
                }
            }
        }
    }

    /**
     * 执行完毕之后执行
     *
     * @throws Exception error
     */
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           DataApiEntity api, Map<String, Object> params,
                           PageResult<Map<String, Object>> pageResult) throws Exception {
        // 数据脱敏
        List<FieldRule> rules = null;
        ApiMaskEntity apiMaskEntity = apiMaskServiceFeign.getApiMaskByApiId(api.getId());
        if (apiMaskEntity != null) {
            rules = apiMaskEntity.getRules();
        }
        try {
            if (Objects.nonNull(rules) && !rules.isEmpty()) {
                // 并行流处理脱敏
                final List<FieldRule> finalRules = rules;
                pageResult.getData().parallelStream().forEach(m -> {
                    for (FieldRule r : finalRules) {
                        if (m.containsKey(r.getFieldName())) {
                            Object obj = m.get(r.getFieldName());
                            if (Objects.nonNull(obj)) {
                                AbstractFactory factory = FactoryProducer.getFactory(r.getCipherType());
                                Crypto crypto = factory.getCrypto(r.getCryptType());
                                String encrypt = crypto.encrypt(String.valueOf(obj));
                                m.put(r.getFieldName(), encrypt);
                            }
                        }
                    }
                });
            }
        } catch (Exception e) {
            log.error("全局异常信息ex={}, StackTrace={}", e.getMessage(), ThrowableUtil.getStackTrace(e));
            throw new DataException("API调用数据脱敏出错");
        }
    }


    public void postLogHandle(HttpServletRequest request, DataApiEntity api, Map<String, Object> params,
                              PageResult<Map<String, Object>> pageResult, Exception e, long start) {
        ApiLogDto apiLog = new ApiLogDto();
        apiLog.setApiId(api.getId());
        apiLog.setCallerIp(IPUtil.getIpAddr(request));
        apiLog.setCallerUrl(request.getRequestURI());
        apiLog.setCallerDate(LocalDateTime.now());
        if (Objects.isNull(e)) {
            apiLog.setCallerSize(pageResult.getData().size());
            apiLog.setStatus(DataConstant.EnableState.ENABLE.getKey());
        } else {
            apiLog.setStatus(DataConstant.EnableState.DISABLE.getKey());
            apiLog.setMsg(e.getMessage());
        }
        apiLog.setCallerParams(JsonCommon.toJson(params));
        apiLog.setTime(System.currentTimeMillis() - start);
        String userId = SecurityUtil.getUserId();
        if (StringUtils.isNotBlank(userId)) {
            apiLog.setCallerId(userId);
        }
        SpringContextHolder.publishEvent(new ApiAccessLogEvent(apiLog));
    }
}
